#include "BayesianHelper.h"
#include "FeedManager.h"
#include "Log.h"
#include "MainEngine.h"
#include "MainThread.h"

#include <list>
#include <sstream>

using namespace std;

set<__Tag*> MainEngine::GetTags ()
{
	LOG1("XMLRPC MainEngine::GetTags");
	
	state->giantLock->Enter();
	map<string,unsigned int> tags = state->mapaCategorias->getMap();	
	state->giantLock->Release();
		
	set<__Tag*> retval;

	map<string,unsigned int>::iterator iter;
	for ( iter = tags.begin() ; iter != tags.end() ; iter++ )
	{
		LOG4("  (",iter->second,") ",iter->first);
		
		__Tag* tmp = new __Tag ();
		tmp->TagID = iter->second;
		tmp->Name  = iter->first;
		retval.insert(tmp);
	}
	
	return retval;
}

set<__Feed*> MainEngine::GetFeeds ()
{
	LOG1("XMLRPC MainEngine::GetFeeds");
	
	state->giantLock->Enter();
	list<FeedItemConsulta> feeds = state->feedManager->ConsultarFeed();	
	state->giantLock->Release();
	
	set<__Feed*> retval;

	list<FeedItemConsulta>::iterator iter;
	for ( iter = feeds.begin() ; iter != feeds.end() ; iter++ )
	{
		LOG6("  (",iter->getId(),") ",iter->getURL()," - ",iter->getDate());
		
		__Feed* tmp = new __Feed ();
		tmp->FeedID   = iter->getId();
		tmp->Name     = iter->getURL();
		tmp->Url      = iter->getURL();
		tmp->DateTime = iter->getDate();

		retval.insert(tmp);
	}

	return retval;
};

list<__News*> MainEngine::GetFeedByNumber ( int feedID , int start , int count )
{
	LOG7("XMLRPC MainEngine::GetFeedByNumber (",feedID,",",start,",",count,")");
	
	list<__News*> retval;

	state->giantLock->Enter();
	list<NewsItemConsulta> news = state->newsManager->consultarNews(start,count,feedID);
	state->giantLock->Release();

	list<NewsItemConsulta>::iterator iter;
	for ( iter = news.begin() ; iter != news.end() ; iter++ )
	{
		LOG2("  ",iter->getTitulo());
	        __News* ns = new __News();
		ns->FeedID = feedID;
	        ns->NewsID = iter->getPosicion();
        	ns->Title = iter->getTitulo();
	        ns->Description = iter->getDescripcion();
	        ns->Author = "";
        	ns->Url = "";
	        ns->DateTime = iter->getFecha();

		list<TCId>* tags = iter->getListaPositivosReferencia();
		list<TCId>::iterator iterTags;
		for ( iterTags = tags->begin() ; iterTags != tags->end() ; iterTags++ )
			ns->Tags.insert(*iterTags);

		retval.push_back(ns);
	}
	
	return retval;
}


void MainEngine::__GetNewsHelper(map<TCId,int>& feedPos, map<TCId,time_t>& maxDate,
		multimap<time_t,NewsItemConsulta>& results,int count,int feedID)
{
	int pos = feedPos[feedID];
	list<NewsItemConsulta> news = state->newsManager->consultarNews
				(pos,count,feedID);

	list<NewsItemConsulta>::iterator iterNews;
	for ( iterNews = news.begin() ; iterNews != news.end() ; iterNews++ )
	{
		results.insert(make_pair(iterNews->getFecha(),*iterNews));
		if ( iterNews->getFecha() < maxDate[feedID] )
			maxDate[feedID] = iterNews->getFecha();
	}

	feedPos[feedID] = pos + count;
}


list<__News*> MainEngine::GetNews  ( int page , int count )
{
	LOG5("XMLRPC MainEngine::GetNews (",page,",",count,")");
	
	list<__News*> retval;
	multimap<time_t,NewsItemConsulta> results;

	state->giantLock->Enter();
	
	list<FeedItemConsulta> feeds = state->feedManager->ConsultarFeed();
	map<TCId,int> 		feedPos;
	map<TCId,time_t>	maxDate;
	
	list<FeedItemConsulta>::iterator iterFeeds;
	for ( iterFeeds = feeds.begin() ; iterFeeds != feeds.end() ; iterFeeds++ )
	{
		feedPos[iterFeeds->getId()] = 0;
		maxDate[iterFeeds->getId()] = numeric_limits<time_t>::max();
	}
 
	for ( iterFeeds = feeds.begin() ; iterFeeds != feeds.end() ; iterFeeds++ )
		__GetNewsHelper(feedPos,maxDate,results,count,iterFeeds->getId());

	int removed = 0;
	while ( removed < page && results.size() != 0 ) 
	{
		time_t maxdate = results.rbegin()->first;
		LOG4("  Removing ",maxdate,": ", results.rbegin()->second.getTitulo());
		results.erase(results.find(results.rbegin()->first));

		map<TCId,time_t>::iterator iterDates;
		for ( iterDates = maxDate.begin() ; iterDates != maxDate.end() ; iterDates++ )
			if ( maxdate <= iterDates->second )
				__GetNewsHelper(feedPos,maxDate,results,count,iterDates->first);
		
		removed++;
	}
	
	state->giantLock->Release();

	LOG1("  Query result");
	
	int taken = 0;
	map<time_t,NewsItemConsulta>::reverse_iterator iter;
	for ( iter = results.rbegin(); iter != results.rend() && taken < count ; iter++, taken++ )
	{
		LOG6("    (",iter->second.getFeedId(),"-",iter->second.getPosicion(),
				") ",iter->second.getTitulo());
		
	        __News* ns = new __News();
		ns->FeedID = iter->second.getFeedId();
	        ns->NewsID = iter->second.getPosicion();
        	ns->Title = iter->second.getTitulo();
	        ns->Description = iter->second.getDescripcion();
	        ns->Author = "";
        	ns->Url = "";
	        ns->DateTime = iter->second.getFecha();

		list<TCId>* tags = iter->second.getListaPositivosReferencia();
		list<TCId>::iterator iterTags;
		for ( iterTags = tags->begin() ; iterTags != tags->end() ; iterTags++ )
			ns->Tags.insert(*iterTags);

		retval.push_back(ns);
	}
		
	return retval;
}

int MainEngine::AddTag ( string name )
{
	LOG3("XMLRPC MainEngine::AddTag (\"",name,"\")");

	unsigned int retval;

	state->giantLock->Enter();
	
	retval = state->mapaCategorias->AgregarCategoria(name);
	state->mapaCategorias->Persistir();
	if ( retval )
	{
		int tmp = state->tagsManager->AgregarCategoria(retval);

		if ( tmp )
		{
			try
			{
				state->bayesianFilter->AddTag(name);
				MainThread::SetDirty();
				LOG1("  Tag added.");
			}
			catch ( ... )
			{
				LOG1("  Exception adding tag to bayesian filter.");
			}
		}
		else
		{
			LOG1("  Error adding tag.");
			retval = 0;
		}
	}
	else
	{
		LOG1("  Error adding tag to tag manager.");
	}
				
	state->giantLock->Release();
	
	return (int) retval;
}

int MainEngine::DelTag ( int tagID )
{
	LOG3("XMLRPC MainEngine::DelTag (",tagID,")");

	state->giantLock->Enter();

	map<unsigned int,string> tags = state->mapaCategorias->getReverseMap();
	if ( tags.find(tagID) == tags.end() )
	{
		state->giantLock->Release();
		return ERROR_TAG_NOT_FOUND;
	}

	try
	{
		state->bayesianFilter->RemoveTag(tags[tagID]);
		MainThread::SetDirty();
	}
	catch ( TagNotExistsException ex )
	{
		LOG1("  Warning: bayesian filter throws TagNotExistsException");
	}

	LOG1("  MapaCategorias::EliminarCategoria");
	state->mapaCategorias->EliminarCategoria(tags[tagID]);
	state->mapaCategorias->Persistir();

	// TODO
	// TagsManager::EliminarTags	
	
	state->giantLock->Release();
	
	return NO_ERRORS;
}

int MainEngine::AddFeed ( string name , string url )
{
	LOG5("XMLRPC MainEngine::AddFeed (\"",name,"\",\"",url,"\")");

	TCId retval;
	Feed feed(0,url,0);
		
	state->giantLock->Enter();
	
	list<FeedItemConsulta> feeds = state->feedManager->ConsultarFeed();	
	list<FeedItemConsulta>::iterator iter;
	for ( iter = feeds.begin() ; iter != feeds.end() ; iter++ )
		if ( iter->getURL() == url )
		{
			state->giantLock->Release();
			return 0;
		}
	
	bool ok = state->feedManager->AgregarFeed(feed,retval);
	state->giantLock->Release();

	if ( ok ) 
		LOG2("  Feed added. ID=",retval);
	else
	{
		LOG1("  Error adding feed.");
		retval = 0;
	}

	return retval;
}

int MainEngine::DelFeed ( int feedID )
{
	LOG3("XMLRPC MainEngine::DelFeed (",feedID,")");

	int retval = 0;
	bool found = false;
	
	state->giantLock->Enter();
	
	list<FeedItemConsulta> feeds = state->feedManager->ConsultarFeed();
	list<FeedItemConsulta>::iterator iter;
	for ( iter = feeds.begin() ; iter != feeds.end() ; iter++ )
		if ( iter->getId() == feedID )
		{
			found = true;
			retval = state->feedManager->EliminarFeed(iter->getPosicionGlobal());
			retval = state->newsManager->EliminarNews(feedID);			
			break;
		}
	
	state->giantLock->Release();
	
	if ( !found )
		LOG3("  ID=",feedID," not found.");
	else if ( !retval )
		LOG1("  Error deleting feed");
	else
		LOG1("  Feed deleted.");
	
	return !retval;
}

bool MainEngine::TagInList ( list<TCId>& lst , int tag )
{
	list<TCId>::iterator iter;
	for ( iter = lst.begin() ; iter != lst.end() ; iter++ )
		if ( *iter == tag ) return true;

	return false;
}

NewsItemConsulta* MainEngine::__GetNews ( int feedID , int newsID )
{
	set<TCPosicionEnLista> pos;
	pos.insert(newsID);
	list<NewsItemConsulta> newsList = state->newsManager->consultarNews(pos,feedID);
	
	if ( newsList.size() == 0 )
		return NULL;
	else
		return new NewsItemConsulta(newsList.front());	
}

int MainEngine::AddRemoveTags ( bool add , int feedID , int newsID , set<int>& tags )
{
	int retval = NO_ERRORS ;
	string strTags;
	stringstream ss;
	set<int>::iterator iter;
	for ( iter = tags.begin() ; iter != tags.end() ; iter++ )
		ss << *iter << ";";

	ss >> strTags;		
	if ( add )
		LOG7("XMLRPC MainEngine::AssignTags (",feedID,",",newsID,",[",strTags,"])");
	else
		LOG7("XMLRPC MainEngine::RemoveTags (",feedID,",",newsID,",[",strTags,"])");

	state->giantLock->Enter();

	LOG1("  Checking tags...");
	map<unsigned int,string> tagNames = state->mapaCategorias->getReverseMap();
	for ( iter = tags.begin() ; iter != tags.end() ; iter++ )
	{
		map<unsigned int, string>::iterator finder = tagNames.find(*iter);
		if ( finder != tagNames.end() )
			LOG4("  (",*iter,") ",tagNames[*iter]);
		else
		{
			LOG3("  Error: tag ",*iter," not found.");
			state->giantLock->Release();
			return ERROR_TAG_NOT_FOUND;
		}
	}
	
	NewsItemConsulta* news = __GetNews(feedID,newsID);
	set<string> words;
	
	if ( news != NULL )
		words = BayesianHelper::ParseWords(news->getTitulo() + " " + 
				news->getDescripcion());
	
	set<string> emptySet;
	set<string> tagName;
	for ( iter = tags.begin() ; news != NULL && iter != tags.end() ; iter++ )
	{
		tagName.clear();
		tagName.insert(tagNames[*iter]);
		
		bool positive = TagInList(*news->getListaPositivosReferencia(),*iter);
		bool negative = TagInList(*news->getListaNegativosReferencia(),*iter);

		if ( add && !positive )
		{
			state->tagsManager->AgregarNoticia(*iter,feedID,newsID,news->getFecha());
			news->getListaPositivosReferencia()->push_back((TCId)*iter);
			state->bayesianFilter->Retrain(words,emptySet,tagName);
			MainThread::SetDirty();
		}
		if ( !add && positive )
		{
			state->tagsManager->SacarNoticia(*iter,feedID,newsID);
			news->getListaPositivosReferencia()->remove((TCId) *iter);
			state->bayesianFilter->Retrain(words,tagName,emptySet);
			MainThread::SetDirty();
		}
		if ( add && negative ) 
		{
			news->getListaNegativosReferencia()->remove((TCId)*iter);
			state->bayesianFilter->Retrain(words,emptySet,tagName);
			MainThread::SetDirty();
		}
		if ( !add && !negative )
		{
			news->getListaNegativosReferencia()->push_back((TCId) *iter);
			state->bayesianFilter->Retrain(words,tagName,emptySet);
			MainThread::SetDirty();
		}

		if ( (add && (!positive || negative)) || (!add && (positive || !negative)) )
			state->newsManager->cambiarListas(feedID,news->getPosicion(),
					*news->getListaPositivosReferencia(),
					*news->getListaNegativosReferencia());
				
	}

	state->giantLock->Release();	
	if ( news != NULL ) 
		delete news;
	else
	{
		retval = ERROR_NEWS_NOT_FOUND;
		LOG1("  The news does not exists in the feed.");
	}
		
	return retval;
}

int MainEngine::AssignTags ( int feedID , int newsID , set<int> tags )
{
	return AddRemoveTags(true,feedID,newsID,tags);
}

int MainEngine::RemoveTags ( int feedID , int newsID , set<int> tags )
{
	return AddRemoveTags(false,feedID,newsID,tags);
}

list<__News*> MainEngine::Search ( set<int> feeds , set<int> tagsPositive ,
			set<int> tagsNegative , time_t since , time_t until )
{
	LOG1("XMLRPC MainEngine::Search (...)");
	list<__News*> retval;
	state->giantLock->Enter();
	
	set<TagItemConsulta> news = state->tagsManager->Consulta(feeds,tagsPositive,tagsNegative,since,until,state->newsManager);
	
	set<TagItemConsulta>::iterator iter;
	for ( iter = news.begin() ; iter != news.end() ; iter++ )
	{
		__News* tmp = new __News();
		tmp->FeedID = iter->getIdFeed();
		tmp->NewsID = iter->getPosicion();
		
		NewsItemConsulta* n = __GetNews(tmp->FeedID,tmp->NewsID);
		if ( n == NULL )
			delete tmp;
		else
		{
	        	tmp->Title = n->getTitulo();
		        tmp->Description = n->getDescripcion();
	        	tmp->Author = "";
	        	tmp->Url = "";
		        tmp->DateTime = n->getFecha();

			list<TCId>* tags = n->getListaPositivosReferencia();
			list<TCId>::iterator iterTags;
			for ( iterTags = tags->begin() ; iterTags != tags->end() ; iterTags++ )
				tmp->Tags.insert(*iterTags);

			delete n;
			retval.push_back(tmp);
		}
	}
	
	state->giantLock->Release();
	return retval;
};
